home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
BBS in a Box 7
/
BBS in a Box - Macintosh - Volume VII (BBS in a Box) (January 1993).iso
/
Files
/
Prog
/
N-P
/
OOP for C.cpt
/
Flavors4C.ƒ
/
life.c
< prev
next >
Wrap
C/C++ Source or Header
|
1988-12-12
|
15KB
|
514 lines
/****************************************************************************/
/* Flavors4C Life example */
/* © Copyright 1988 Peter Ohler, All Rights Reserved */
/****************************************************************************/
/*
This file is an example of how to use some of the functions in Flavors4C.
This example is not meant to be an example of good programming style or of
the most efficient way to implement the sample problem.
The task that this example attempts to perform is to model a closed
ecological system using object oriented code. In this simple eco-system, the
environment consists of a two dimensional array which is inhabited by the
objects or creatures that make up the eco-system. Each creature can move and
can interact with other creatures in certain ways. The creatures defined in
the following code are Rabbits, Coyotes, and Road Runners. Each of these
creatures has some common behavior that is shared through the inherited
flavors. Each one also has some special behavior that it inherites from some
"mixin" flavors. In order to get things moving a clock is used to broadcast
a message to every object that tells the object to perform the "action" for
that object. The "action" for each is how the creature reacts to it's
surrounding.
The programmer is encouraged to expand and modify this example in order to
develop a better understanding of how Flavors4C is used.
*/
/* The "string", "unix", "storage", and "stdio" libraries are needed for the
evaluation version of Flavors4C. */
/* The following libraries should be included in the LIFE.Project using
THINK C: MacTraps, stdio, storage, strings, Flavors4C, math, unix */
#include <stdio.h> /* required to use Flavors4C */
#include <unix.h> /* required to use Flavors4C */
#include "Flavor4C.h"
#define ROWS 8
#define COLS 18
#define DURATION 10
#define COYOTES 6
#define RABBITS 3
#define ROAD_RUNNERS 6
#define COL_OFFSET 60
#define NULL 0
Instance **world;
/* spot() is used to see if something is in the (row, col) of the world
array. The returned value is a pointer to the (row, col) so that the value
can be changed easily.
*/
Instance**
spot(row, col)
int row, col;
{
return &(world[row*COLS + col]);
}
main()
{
int r, c;
Instance **op;
makeWorld();
gotoxy(0,0);
makeClasses();
makeCreatures();
startClock();
gotoxy(0,ROWS+2);
}
/* This function allocates the memory for the world array and then draws the
board on the screen.
*/
makeWorld()
{
int r, c;
Instance **op;
char *malloc();
if ((world = (Instance**)malloc(sizeof(Instance*)*ROWS*COLS)) == NULL) {
printf("Could not allocate memory for the world array!");
exit(0);
}
for(r=0; r<ROWS; r++) {
for(c=0; c<COLS; c++) {
*spot(r, c) = NULL;
}
}
gotoxy(0 + COL_OFFSET,0);
for(c=0; c<COLS+2; c++) printf("-");
for(r=1; r<=ROWS; r++) {
gotoxy(0 + COL_OFFSET,r);
printf("|");
for(c=0; c<COLS; c++) printf(" ");
printf("|");
}
gotoxy(0 + COL_OFFSET,ROWS+1);
for(c=0; c<COLS+2; c++) printf("-");
}
/* This creates all the classes for the objects. */
makeClasses()
{
crThing();
crMobile();
crSpeedy();
crRepro();
crPredator();
crCoyote();
crRabbit();
crRunner();
}
/* This starts the action and continues it for the duration. */
startClock()
{
int t;
char c = 'P';
for(t=0; t<DURATION; t++) {
BroadCast("Thing", "action");
}
}
/* The object drawing routine. Since all objects should be drawn on the
screen this function is used whenever the object must be drawn. It is
attached, as a method to the "Thing" flavor which all objects are built on in
this system. The function determines where the object is to be drawn from
the row and col instance variables of the object and then it draws the
character which represents the type of object that must be drawn. The symbol
variable of each flavor is used as the character that is drawn on the screen.
Since all objects of a given type should look alike the symbol variable is
defined as a class variable. When the object is drawn the world array is
updated so that the world array will always be consistant with the display.
*/
Pntr
draw_thing(self)
Instance *self;
{
int row, col;
col = *(int*)GetVar(self, "col");
row = *(int*)GetVar(self, "row");
gotoxy(col + 1 + COL_OFFSET, row + 1);
printf("%c", *(char*)GetVar(self, "symbol"));
*spot(row, col) = self;
return (Pntr)self;
}
/* The object erasing routine. This method is the inverse of the draw method
described above.
*/
Pntr
erase_thing(self)
Instance *self;
{
int row, col;
col = *(int*)GetVar(self, "col");
row = *(int*)GetVar(self, "row");
gotoxy(col + 1 + COL_OFFSET, row + 1);
printf(" ");
*spot(row, col) = NULL;
return NULL;
}
/* This creates the Flavor for a basic "Thing". */
crThing()
{
Flavor *thing; /* use of a pointer is faster than using a string */
thing = DefFlavor("Thing", "");
/* The row and col instance varaibles are added to the flavor "Thing" and the
default values are set to -1. */
AddInstanceVar(thing, "row", ALL, int, -1);
AddInstanceVar(thing, "col", ALL, int, -1);
/* After a "Thing" is created the object must be placed in the array and
drawn on the screen. */
DefMethod(thing, AFTER, "init", draw_thing);
/* Before we remove an object it must be removed from the array and erased. */
DefMethod(thing, BEFORE, "kill", erase_thing);
/* Since the clock will broadcast to all instances of "Thing" we want to make
sure that the flavor has a method for "action" when it is called. */
RequiredMethods(thing, "action");
/* We would like all the inherited actions to be performed for each object,
as well as the before, after, and whoppers. The DAEMON_OR combination will
allow this behavior. */
MethodCombination("Thing", DAEMON_OR, BASE_FLAVOR_LAST, "action");
/* Complete the definition of "Thing" by compiling the flavor. Keeping all
the definition for "Thing" in one function will avoid confusion. */
CompileFlavor(thing);
}
/* This function is used to determine which way a mobile thing will move. We
would like random changes in direction when an object can not continune on
it's current course. First all the possible directions are checked. Any
adjoining space that is empty is stored in a set of arrays. The new
direction is selected by randomly by picking an index into the array at
random. */
int
pick_dir(row, col, dr, dc)
int row, col, *dr, *dc;
{
int r, c, newr, newc;
int xr[9], xc[9], i, pick;
i = 0;
for(r=-1; r<2; r++) {
for(c=-1; c<2; c++) {
if ((!r && !c) || (r == *dr && c == *dc)) continue;
newr = (row + r + ROWS) % ROWS;
newc = (col + c + COLS) % COLS;
if (*spot(newr, newc) == NULL) {
xr[i] = r;
xc[i++] = c;
}
}
}
if (i > 0) {
pick = rand() % i;
*dr = xr[pick];
*dc = xc[pick];
}
return i; /* used to indicate success */
}
/* This is the primary "action" method for the Mobile_Mixin. Any object that
will move should have this method included. It is attached to the
Mobile_mixin flavor. An attempt to move in the current direction is always
tried first. If the adjacent space for the move is not empty then a new
direction is chosen. */
Pntr
move_it(self)
Instance *self;
{
int *dr, *dc, *row, *col, newr, newc;
Instance **newpos;
dr = (int*)GetVar(self, "dir-row");
dc = (int*)GetVar(self, "dir-col");
row = (int*)GetVar(self, "row");
col = (int*)GetVar(self, "col");
newr = (*row + *dr + ROWS) % ROWS;
newc = (*col + *dc + COLS) % COLS;
newpos = spot(newr, newc);
if ((*dr != 0 || *dr != 0) && *newpos == NULL) {
*row = newr;
*col = newc;
} else if (pick_dir(*row, *col, dr, dc)) {
*row = (*row + *dr + ROWS) % ROWS;
*col = (*col + *dc + COLS) % COLS;
}
return NULL;
}
/* This creates the Flavor for a "Mobile_mixin". */
crMobile()
{
DefFlavor("Mobile_mixin", "");
/* dir-row and dir-col are used to remember the last direction that the
object was moving */
AddInstanceVar("Mobile_mixin", "dir-row", ALL, int, 0);
AddInstanceVar("Mobile_mixin", "dir-col", ALL, int, 0);
/* Since any mobile object must have the features found in "Thing", "Thing"
is made a required flavor. */
RequiredFlavors("Mobile_mixin", "Thing");
/* Before an object is moved we must erase it from it's current position and
then after it is moved we can redraw it. */
DefMethod("Mobile_mixin", BEFORE, "action", erase_thing);
DefMethod("Mobile_mixin", PRIMARY, "action", move_it);
DefMethod("Mobile_mixin", AFTER, "action", draw_thing);
CompileFlavor("Mobile_mixin");
}
/* This is the Whopper for the "Speedy_mixin". The Speedy_Mixin causes the
action of the object to be executed twice, no matter what the action is.
This behavior is created using a Whopper. Note the two arguments required by
a function that is used as a Whopper. */
Pntr
stutter(methods, self)
Pntr methods;
Instance *self;
{
ContinueWhopper(methods, self);
return ContinueWhopper(methods, self);
}
/* This creates the Flavor for a "Speedy_mixin". */
crSpeedy()
{
DefFlavor("Speedy_mixin", "");
/* Lets make the Mobile_mixin included whenever the Speedy_mixin is used. */
IncludedFlavors("Speedy_mixin", "Mobile_mixin");
DefMethod("Speedy_mixin", WHOPPER, "action", stutter);
CompileFlavor("Speedy_mixin");
}
/* This function is used to find a mate for things that can reproduce. First
each spot around the object is checked. If one of the spots is empty and one
of the spots has an object of the same class then a true is returned.
(interbreeding amoung different species is not allowed) */
int
find_mate(kind, row, col, kidr, kidc)
Flavor *kind;
int row, col, *kidr, *kidc;
{
int r, c, found, newr, newc;
Instance *mate;
*kidr = row;
*kidc = col;
found = 0;
for(r=-1; r<2; r++) {
for(c=-1; c<2; c++) {
if (!r && !c) continue;
newr = (row + r + ROWS) % ROWS;
newc = (col + c + COLS) % COLS;
if ((mate = *spot(newr, newc)) == NULL) {
*kidr = newr;
*kidc = newc;
} else if (!Send(mate, "has-flavor", kind)) {
found = 1;
}
}
}
if (found && (*kidr != row || *kidc != col)) {
return 1;
}
return 0;
}
/* This is the method that allows things to reproduce. First check for a
mate and if possible create a new instance of the same type. */
Pntr
mate(self)
Instance *self;
{
int row, col, kidr, kidc;
Instance *kid;
Flavor *flavor;
row = *(int*)GetVar(self, "row");
col = *(int*)GetVar(self, "col");
flavor = (Flavor*)Send(self, "instance-of");
if (find_mate(flavor, row, col, &kidr, &kidc)) {
kid = MakeInstance(flavor, 0);
*(int*)GetVar(kid, "row") = kidr;
*(int*)GetVar(kid, "col") = kidc;
Send(kid, "init");
}
return NULL;
}
/* This creates the Flavor for a "Reproduce_mixin". */
crRepro()
{
DefFlavor("Reproduce_mixin", "");
RequiredFlavors("Reproduce_mixin", "Thing");
DefMethod("Reproduce_mixin", PRIMARY, "action", mate);
CompileFlavor("Reproduce_mixin");
}
/* This functions finds food for things that eat other things. Food is found
be checking the spots around the object. If one is occupied by some other
type of object then that spot is returned as the meal. The kind of object is
checked so that we don't have any canabalism. Food is chosen at random so as
not to be biased in one direction. Also the object will move toward the meal
since the direction is updated. */
Instance*
find_food(kind, row, col, dr, dc)
Flavor *kind;
int row, col, *dr, *dc;
{
int r, c, newr, newc;
int xr[9], xc[9], i, pick;
Instance *meal, *xmeal[9];
i = 0;
for(r=-1; r<2; r++) {
for(c=-1; c<2; c++) {
if (!r && !c) continue;
newr = (row + r + ROWS) % ROWS;
newc = (col + c + COLS) % COLS;
if ((meal = *spot(newr, newc)) != NULL &&
!Send(meal, "has-flavor", kind)) {
xmeal[i] = meal;
xr[i] = r;
xc[i++] = c;
}
}
}
if (i > 0) {
pick = rand() % i;
*dr = xr[pick];
*dc = xc[pick];
return xmeal[pick];
}
return NULL;
}
/* This is the method that allows predators to eat other things. If a meal
is found then it is sent a kill message and it is removed from the world and
deleted. */
Pntr
eat(self)
Instance *self;
{
int *dr, *dc, *row, *col, newr, newc;
Instance *meal;
dr = (int*)GetVar(self, "dir-row");
dc = (int*)GetVar(self, "dir-col");
row = (int*)GetVar(self, "row");
col = (int*)GetVar(self, "col");
if (meal = find_food(Send(self, "instance-of"), *row, *col, dr, dc))
Send(meal, "kill");
return NULL;
}
/* This creates the Flavor for a "Predator_mixin". */
crPredator()
{
DefFlavor("Predator_mixin", "");
RequiredFlavors("Predator_mixin", "Thing");
DefMethod("Predator_mixin", PRIMARY, "action", eat);
CompileFlavor("Predator_mixin");
}
crCoyote()
{
/* Define what messages this object will respond to. */
DefFlavor("Coyote", "Predator_mixin Mobile_mixin Thing");
/* Reset the symbol instance variable for this type of object. */
AddClassVar("Coyote", "symbol", ALL, char, '∑');
MethodCombination("Coyote", DAEMON_OR, BASE_FLAVOR_LAST, "action");
CompileFlavor("Coyote");
}
crRabbit()
{
/* Define what messages this object will respond to. */
DefFlavor("Rabbit", "Reproduce_mixin Mobile_mixin Thing");
/* Reset the symbol instance variable for this type of object. */
AddClassVar("Rabbit", "symbol", ALL, char, '¥');
MethodCombination("Rabbit", DAEMON_OR, BASE_FLAVOR_LAST, "action");
CompileFlavor("Rabbit");
}
crRunner()
{
/* Define what messages this object will respond to. */
DefFlavor("Road_Runner", "Speedy_mixin Mobile_mixin Thing");
/* Reset the symbol instance variable for this type of object. */
AddClassVar("Road_Runner", "symbol", ALL, char, 'Ω');
MethodCombination("Road_Runner", DAEMON_OR, BASE_FLAVOR_LAST, "action");
CompileFlavor("Road_Runner");
}
/* This function will create all the instances of each Flavor. First seed
the random number generator based on the time. Then for every creation of a
creature find an unoccupied spot and place the object in the spot by setting
the row and col of the object. Note that the init flag for MakeInstance() is
0 so that the init method will be delayed until explicitly sent or until the
first a message is sent to the object. */
makeCreatures()
{
int i, r, c, cnt, *dr, *dc;
Instance **pos, *obj;
srand((unsigned)(time(NULL) % 10000 + 1));
for(i=0; i<COYOTES; i++) {
for(cnt=0; cnt<ROWS*COLS; cnt++) {
/* a limit on attempts to find a clear spot. */
r = rand() % ROWS;
c = rand() % COLS;
if (*spot(r, c) == NULL) {
obj = MakeInstance("Coyote", 0);
*(int*)GetVar(obj, "row") = r;
*(int*)GetVar(obj, "col") = c;
Send(obj, "init"); /* just to see where the coyotes are */
break;
}
}
}
for(i=0; i<RABBITS; i++) {
for(cnt=0; cnt<ROWS*COLS; cnt++) {
r = rand() % ROWS;
c = rand() % COLS;
if (*spot(r, c) == NULL) {
obj = MakeInstance("Rabbit", 0);
*(int*)GetVar(obj, "row") = r;
*(int*)GetVar(obj, "col") = c;
break;
}
}
}
for(i=0; i<ROAD_RUNNERS; i++) {
for(cnt=0; cnt<ROWS*COLS; cnt++) {
r = rand() % ROWS;
c = rand() % COLS;
if (*spot(r, c) == NULL) {
obj = MakeInstance("Road_Runner", 0);
*(int*)GetVar(obj, "row") = r;
*(int*)GetVar(obj, "col") = c;
break;
}
}
}
}